/**
    Implementation of the communication functionalities of LibLC.
    (Differs from the previous version, since it not distinguish between synchronous or
     asynchronous channel, hence there is no code replication).
*/
#include "channel.h"
#include "shm_common.h"
#include <sys/shm.h>
#include <pthread.h>


#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>


// returns the address of the i-th message of channel ch
#define MSG(ch,i) ( (char*) ch + sizeof(*ch) + ch->fixedsize*i )
// returns the address of the i-th validity byte of channel ch
#define VALB(ch,i) ( (char*) ch + sizeof(*ch) + ch->fixedsize*(ch->k+ch->j) + sizeof(bool)*i )

/*
    General procedure for the creation of a communication channel.
    If it is a synchronous channel, it will have k=0; otherwise is an

    asynchronous channel.
    The channel will contain k+1+j vtgs; j has to be at least 1, for the channel to be safe.
*/
inline struct channel_t * __LCcreate( key_t key, size_t size, int k, int j, ChannelType type )
{
    struct channel_t *ch;
    int fixedsize = size + ( LINE_SIZE - size%LINE_SIZE ); //padding to cache line size

    /*
        Create a shared memory segment for the channel.
        It's structured in the following way
        - the channel descriptor
        - the space for target variables (k+1+j)
        - the validity bit (actually byte) vector
    */
    /* TODO (dematteis#1#): Vailidty byte vector da allineare??? */
    int id = shmget( key, sizeof(*ch) + fixedsize*(k+1+j)+sizeof(bool)*(k+1+j), IPC_CREAT | 0700 );
    if( id == -1 )
    {
        fprintf(stderr, "[LCcreate] shmget(%x[%d], %d, %x): %s\n", key, key, (int) ( sizeof(*ch) + fixedsize*(k+1+j) ), IPC_CREAT | 0700, strerror(errno) );
        return NULL;
    }

    //attach of the channel, in order to have the base address of the channel descriptor
    ch = shmat( id, 0, 0 );
    if( ch == (void*) -1 )
    {
        printf( "[LCcreate] shmat(%d, %d, %d): %s\n", id, 0, 0, strerror(errno) );
        return NULL;
    }

    // setting channel type
    ch->type = type;

    // setting channel key and id
    ch->key = key;
    ch->id = id;
    // initing mutex and condition variable
    // (it's mandatory to set them as shared objects!)
    //mutex_init( & ch->mutex );
    //cond_init( & ch->cond );
    /*
        For the creation of condition variable, we can't use a traditional approach (that will
        allocate memory in the heap, hence not in the shared memory segment).
    */
    initLockVariable(&(ch->mutex));
    initCondVariable(&(ch->cond_tosender));
    initCondVariable(&(ch->cond_toreceiver));

    // setting other fields of the channel
    ch->size = size;
    ch->fixedsize = fixedsize;
    ch->messages = 0;
    ch->k = k;
    ch->j = 1+j;
    ch->nextVtg = 0;
    ch->currVtg = -1;
    ch->altKey = 0;
    return ch;
}

/*
    Creation of a synchronous channel. It's a simple wrapper for the
    general procedure __LCcreate
*/
struct channel_t *LCcreate_ss( key_t key, size_t size,  int j )
{
    return __LCcreate(  key,  size,  0,  j, SYNCHRONOUS_SYMMETRIC );
}

/*
    Creation of a asynchronous channel. It's a simple wrapper for the
    general procedure __LCcreate
*/
struct channel_t *LCcreate_as( key_t key, size_t size, int k, int j )
{
    return __LCcreate(  key,  size,  k,  j , ASYNCHRONOUS_SYMMETRIC);
}

/*
    Returns the size of the channel data type
*/
inline size_t LCgetChannelTypeSize( struct channel_t *ch )
{
    return ch->size;
}


inline bool LCreadyToReceive( struct channel_t *ch )
{
    /*
        We have to check if the number of messages is greater than zero but also that
        the relative validity byte is true (otherwise the correspondent message is meaningless)
    */
    int currVtg = ( ch->currVtg + 1 ) % (ch->k + ch->j);
    return (ch->messages > 0 && *VALB(ch,currVtg));
}


inline bool LCreadyToSend( struct channel_t *ch )
{
    /*
    In case of synchronous channel, we can send if
    messages=0
    */
    return ch->messages <= ch->k;
}

/*
    Maps a shared channel on the virtual memory
*/
struct channel_t *LCattach( key_t key)
{
    /* we have to acquire the shared segmet twice: the first one to get the channel * struct.
    Then we know the properties of the channel (type, size, asynch degree...) and we can attach it
    properly*/
    struct channel_t *tmpCh, *ch;

    //acquire the channel descriptor infos
    int id = shmget( key, sizeof(*ch), 0700 );
    if( id == -1 )
    {
        fprintf(stderr, "[LCattach] shmget(): %s\n", strerror(errno) );
        return NULL;
    }

    tmpCh = shmat( id, 0, 0 );
    if( ! tmpCh )
    {
        fprintf(stderr, "[LCattach] shmat(): %s\n", strerror(errno) );
        return NULL;
    }


    // now we can acquire the whole channel descriptor
    key = tmpCh->key;
    size_t size = tmpCh->fixedsize;
    int k = tmpCh->k; //0 in case of synchronous channel
    int j = tmpCh->j-1;

    id = shmget( key, sizeof(*ch) + size*(k+1+j)+sizeof(bool)*(k+1+j), 0700 );
    // freeing tmp channel
    /* TODO (dematteis#1#): perche fa la detatch e non la destroy? */
    shmdt( tmpCh );
    if( id == -1 )
    {
        fprintf( stderr, "[LCattach] shmget(): %s\n", strerror(errno) );
        return NULL;
    }

    ch = shmat( id, 0, 0 );
    if( ch == (void*) -1 )
    {
        fprintf( stderr, "[LCattach] shmat(): %s\n", strerror(errno) );
        return NULL;
    }
    return ch;
}


/*
    Destroys a channel
*/
void LCdestroy( struct channel_t *ch )
{
    //destroy mutex and condition variable
    //pthread_mutex_destroy( & ch->mutex );
    //pthread_cond_destroy( & ch->cond );
    // destroyCondVariable(&ch->cond_tosender);
    //destroyCondVariable(&ch->cond_toreceiver);
    //Mark  the  segment to be destroyed.
    shmctl( ch->id, IPC_RMID, 0 );
}

/*
    Removes a channel from the virtual memory
*/
void LCdetatch( struct channel_t *ch )
{
    //detatch the shared memory segment
    shmdt( ch );
}

/*
    Sends a message on a channel
*/
void LCsend( struct channel_t *ch, char *msg )
{


    __LClockChannel( ch );

    /*
        Copy of the message. Since memcpy has problem with the allignment,
        we copy all the blocks using it and then what remains byte per byte.
    */
    memcpy( MSG(ch,ch->nextVtg), msg, ch->fixedsize - LINE_SIZE );
    int i;
    for( i = ch->fixedsize - LINE_SIZE; i < ch->size; i ++ )
    {
        *( MSG(ch,ch->nextVtg)+i ) = *( msg+i );
    }

    //update infos
    ch->messages ++;
    //set the validity byte
    *VALB(ch,ch->nextVtg)=true;
    ch->nextVtg = ( ch->nextVtg + 1 ) % ( ch->k + ch->j ); //k=0 for synchr channel

    //wakeup partner (if necessary)
    notify(&(ch->cond_toreceiver));


    /*
        alternative command: if there is the altKey field setted means that
        someone is polling on the alternative command. Wakeup it...
    */
    /* TODO (dematteis#1#): che se fa con l'alternativo??? */

    if( ch->altKey )
    {
        __LCwakeUpAlternative( ch->altKey );
    }

    //wait if necessary
    while( ! LCreadyToSend(ch) )
        waitNotify(&(ch->cond_tosender), &(ch->mutex));

    __LCunlockChannel( ch );
}

/*
    Receives a message from the channel
*/
char *LCreceive( struct channel_t *ch )
{
    char *vtg;

    __LClockChannel( ch );

    //wait if necessary
    while( ! LCreadyToReceive(ch) )
        waitNotify(&(ch->cond_toreceiver), &(ch->mutex));

    //update info
    ch->currVtg = ( ch->currVtg + 1 ) % (ch->k + ch->j);
    //get the pointer to the message
    vtg = MSG(ch, ch->currVtg);

    /*reset the validity byte: please note that this has the same problem of possibile overwriting of variables
        that we try to solve using J additional target variable.
        A better solution will be to have only the validity byte that will be resetted by the receiver
        when the usage of a particular target variable is terminated*/
    *VALB(ch,ch->currVtg)=false;

    ch->messages --;

    //wake up the partner if necessary

    notify(&(ch->cond_tosender));

    __LCunlockChannel( ch );

    return vtg;
}

/*
    Prints debugging info about a channel
*/
void printCH_as( struct channel_t *ch )
{
    int i;

    printf( "struct channel_t: %p\n"
            "    key: %d\n"
            "     id: %d\n"
            "   size: %d\n"
            "fxdsize: %d\n"
            "      k: %d\n"
            "      j: %d\n"
            "   next: %d\n"
            "   curr: %d\n"
            "   msgs: %d\n"
            "    alt: %d\n"
            "    tosender: %c\n"
            "    toreceiver: %c\n"
            "[%d bytes + %d per message: %d]\n",
            ch,
            ch->key,
            ch->id,
            ch->size,
            ch->fixedsize,
            ch->k,
            ch->j,
            ch->nextVtg,
            ch->currVtg,
            ch->messages,
            ch->altKey,
            ch->cond_tosender, ch->cond_toreceiver,
            (int) sizeof(*ch), ch->size, (int) sizeof(*ch)+ch->size );

    for( i = 0; i < (ch->k + ch->j); i ++ )
    {
        printf( " msg[%d]: %s\n", i, MSG(ch,i) );
    }
}
